#include <algorithm>
#include "my_assert.h"
#include "disk_storage.h"
#include "../logger/logger.h"

using std::list;
using std::string;

DiskStorage::DiskStorage(uint64_t size, const string& filename):
	pstorages_(nullptr),
	storage_count_(0),
	mapped_storage_idx_(-1),
	mapping_(filename, size) {
	LOG_FUNCTION("");	

	my_assert(size);

	storage_count_ = (size_t) ((size + MAX_MMAP_SIZE - 1) / MAX_MMAP_SIZE);
	pstorages_ = new Storage[storage_count_];
	for (size_t i = 0; i < storage_count_; ++i) {
		pstorages_[i].pstorage_ = new MemoryStorage(MAX_MMAP_SIZE, true);
		pstorages_[i].offset_ = i * MAX_MMAP_SIZE;
		pstorages_[i].size_ = size - pstorages_[i].offset_ < MAX_MMAP_SIZE ? 
		    size - pstorages_[i].offset_ : MAX_MMAP_SIZE;
	}
}
	

DiskStorage::~DiskStorage() {
	LOG_FUNCTION("");	
	ASSERT_OK(); // it'll check all pointers that we are going to delete
	for (size_t i = 0; i < storage_count_; ++i) 
		delete pstorages_[i].pstorage_;
	delete [] pstorages_;
}

StorageChunk *DiskStorage::Alloc(uint64_t size,
    const list<StorageChunk**, FSBAllocator<StorageChunk**>>& currently_unused,
    std::function<void (StorageChunk**)> delete_func) {
	LOG_FUNCTION("");	
	ASSERT_OK();
	
	list<StorageChunk**, FSBAllocator<StorageChunk**>> empty_list;
	StorageChunk *ret = nullptr;
	size_t swap_idx = 0;
	for (size_t i = 0; i < storage_count_ && !ret; ++i) {
		my_assert(pstorages_[i].pstorage_);
		ret = pstorages_[i].pstorage_->Alloc(size, empty_list, delete_func); 
		//LESHCH можно было добавить map, содержащий необходимые размеры и искать в нем за O(log(n)), 
		//а тут перебор всех кусков свопа и попытка выделить, пока не получится
		if (ret)
			swap_idx = i;
	}

	for (size_t i = 0; i < storage_count_ && !ret; ++i) {
		auto ippsc = currently_unused.end();
		while ((ippsc = std::find_if( //LESHCH отличная мотивация
		    currently_unused.begin(),
		    currently_unused.end(),
		    [&](StorageChunk** const& ppsc) { 
			    my_assert(ppsc);
			    my_assert(*ppsc);
			    return ((DiskStorageChunk*)(*ppsc))->swap_idx_ == i;
		    })) != currently_unused.end() && !ret) {
			if (ippsc == currently_unused.end()) 
				continue;
			my_assert(*ippsc);
			delete_func(*ippsc);
			ret = pstorages_[i].pstorage_->Alloc(size, empty_list,
			    delete_func);
			if (ret) 
				 swap_idx = i;
		}
	} 

	
	if (!ret)
		return nullptr;

	DiskStorageChunk *pret = new DiskStorageChunk;
	pret->st_chunk_ = (MemoryStorageChunk*)ret;
	pret->swap_idx_ = swap_idx;

	return pret;
}

void *DiskStorage::Load(StorageChunk *pst_data) {
	LOG_FUNCTION("");	
	ASSERT_OK(); // It'll check pointers
	my_assert(pst_data);
	DiskStorageChunk *pdisk_st = (DiskStorageChunk*) pst_data;
	my_assert(pdisk_st->swap_idx_ < storage_count_);

	if (mapped_storage_idx_ != -1 && mapped_storage_idx_ != pdisk_st->swap_idx_) 
		mapping_.Unmap();	
	
	if (mapped_storage_idx_ != pdisk_st->swap_idx_) {
		mapping_.MapRW(pstorages_[pdisk_st->swap_idx_].offset_,
		    pstorages_[pdisk_st->swap_idx_].size_);
		mapped_storage_idx_ = pdisk_st->swap_idx_;
	}

	
	my_assert(mapping_.GetCurrentMap());
	
	char *ret = (char*)pstorages_[pdisk_st->swap_idx_].pstorage_->
	    Load(((DiskStorageChunk*)pst_data)->st_chunk_) + 
	    (size_t)mapping_.GetCurrentMap();
	
	my_assert(ret);

	return ret;
	
}

void DiskStorage::Free(StorageChunk *pst_data) {
	LOG_FUNCTION("");	
	ASSERT_OK();
	my_assert(pst_data);
	DiskStorageChunk *pdisk_st = (DiskStorageChunk*) pst_data;
	my_assert(pdisk_st->swap_idx_ < storage_count_);
	my_assert(pstorages_);
	my_assert(pstorages_[pdisk_st->swap_idx_].pstorage_);
	pstorages_[pdisk_st->swap_idx_].pstorage_->Free(pdisk_st->st_chunk_);
	
}

void DiskStorage::AssertOk() {
	for (size_t i = 0; i < storage_count_; ++i) {
		my_assert(pstorages_);
		my_assert(pstorages_[i].pstorage_);
		pstorages_[i].pstorage_->AssertOk();
	}
	my_assert(storage_count_ != -1 || storage_count_ >= 0);
}
